package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 聊天室服务端
 */
public class Server {
    //
    private ServerSocket serverSocket;

    // 定义一个集合放所有客户端的输出流
//     private List<PrintWriter> allOut = new ArrayList<>();
    // 换为使用map去键对值
    // 第一个存昵称，第二个存输出流
    private Map<String, PrintWriter> allOutMap = new HashMap<>();
    // 构造器
    public Server() {
        try {
            // 服务器启动
            System.out.println("服务器启动中...");
            serverSocket = new ServerSocket(8088);
            // 如果端口被占用，那么会抛出异常，也不会执行下面的话
            // 可以使用win+r打开终端，输入netstat -ano，查看端口占用情况
            System.out.println("服务器启动成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void start() {

        try{
            System.out.println("等待客户端连接");
            // 实现客户端与服务端的连接
            // tcp第二次握手
            /*
            * accept()方法会阻塞，直到有客户端连接
            * 该方法返回一个socket对象，该对象代表客户端，用于双向通讯
             */
            while(true){
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了:"+socket);

                // 创建线程，将socket传递到线程中,启动一个线程与该客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                Thread thread = new Thread(clientHandler);
                thread.start();

                /*// 创建一个字节输入流，用于读取客户端发送的数据
                InputStream inputStream = socket.getInputStream();

                // 创建字节转换输入流
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);

                // 创建缓冲输入流
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                // 一直读
                while(true){
                    String data = bufferedReader.readLine();
                    if(data!=null){
                        System.out.println("客户端发送过来的消息："+ data);
                    }else{
                        break;
                    }
                }*/
            }

            // Socket socket = serverSocket.accept();
            // System.out.println("一个客户端连接了");

//            int in = inputStream.read();
//            System.out.println(in);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    // 创建内部类线程与指定的客户端实现交互
    private class ClientHandler implements Runnable{
        // 私有成员变量存储传递过来的socket对象
        private Socket socket;
        private String host;
        private String port;
        private String nickName;

        // 构造器
        public ClientHandler(Socket socket) {
            this.socket = socket;
            // 获取主机信息
            this.host = socket.getInetAddress().getHostAddress();
            // 获取主机端口
            this.port = String.valueOf(socket.getPort());
        }
        // 重写线程的run方法
        @Override
        public void run() {
            PrintWriter printWriter = null;
            try{
                // 创建一个字节输入流，用于读取客户端发送的数据
                InputStream inputStream = socket.getInputStream();

                // 创建字节转换输入流
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);

                // 创建缓冲输入流
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                // 创建输出流，用于向客户端发送数据
                OutputStream outputStream = socket.getOutputStream();
                OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
                BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
                printWriter = new PrintWriter(bufferedWriter,true);

                // 单独读取来自客户端发过来的第一个字符串作为昵称
                nickName = bufferedReader.readLine();
/*
                // 将所有的输出流存储到集合里面
                // 当客户端一次上线的非常多的人的时候，就会导致内存溢出
                // allOut.add(printWriter);
                // 这里需要添加一个同步锁
                // 这里的锁对象不能是this,因为创建线程的时候是创建了一个新的对象，所以this不是同一个对象
                // 所以即时使用this也不能解决问题所在，
                // 记住一个原则：抢谁就锁谁*/
//                synchronized (allOut){
//                    allOut.add(printWriter);
//                }

                synchronized (allOutMap) {
                    allOutMap.put(nickName, printWriter);
                }

                // 通知所有人上线了
//                sendMessage(nickName + "上线了，当前在线人数" + allOut.size());
                sendMessage(nickName + "上线了，当前在线人数" + allOutMap.size());
                // 一直读
                while(true){
                    String data = bufferedReader.readLine();
                    if(data!=null){
                       /* System.out.println(nickName +"["+host +":"+port+"]说：" + data);
                        // printWriter.println(data);
                        // 遍历集合，将消息发送给所有客户端
                        for(PrintWriter pw:allOut){
//                            pw.println(nickName + "上线了");
                            pw.println(nickName+"说：" + data);
                        }*/
                        // 代码重复，使用方法
                        // sendMessage(nickName +"说：" + data);

                        // 如果聊天内容以@开始则为私聊功能，使用正则表达式
                        if(data.matches("^@.+:.*$")){

                            // 截取昵称
                            String toNickName = data.substring(1,data.indexOf(":"));
                            // 根据对方发昵称获取输出流
                            PrintWriter pwNickName = allOutMap.get(toNickName);
                            // 判断对方是否在线
                            if(pwNickName == null){
                                // 自己的输出流
                                printWriter.println("对方不在线");
                                continue;
                            }else{
                                // 截取私聊内容
                                String toDataMessage = data.substring(data.indexOf(":")+1);
                                pwNickName.println(nickName + "悄悄对你说：" + toDataMessage);
                            }


                        }else{
                            // 用当前用户的昵称获取输出流
                            PrintWriter pwNickName = allOutMap.get(nickName);
//                            sendMessage(nickName +"说：" + data);
                            pwNickName.println("私聊格式不正确！");
                        }
                        sendMessageToSomeone(data);
                    }else{
                        break;
                    }
                }

            }catch (Exception e){
                System.out.println("客户端["+host+":"+port+"]下线了");
            }finally {
                // 处理客户端下线，
                // 将客户端冲共享集合中移除
                // 这里也需要加上同步锁
               /* synchronized (allOut){
                    allOut.remove(printWriter);
                    try{
                        // 通知所有人下线了
                        sendMessage(nickName + "下线了，当前在线人数" + allOut.size());
                        // 关闭资源
                        socket.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }*/
                synchronized (allOutMap) {
                    allOutMap.remove(nickName);
                    try {
                        // 通知所有人下线了
                        sendMessage(nickName + "下线了，当前在线人数" + allOutMap.size());
                        // 关闭资源
                        socket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }


            }


        }
        // 将消息发送给所有客户端
        private void sendMessage(String message){
            System.out.println(message);
            // printWriter.println(data);
            // 遍历集合，将消息发送给所有客户端
            /*
            注意：
            1. 遍历集合的时候，不能使用集合的remove方法，否则会抛出并发修改异常

             */
         /*   synchronized(allOut){
                for(PrintWriter pw:allOut){
//                            pw.println(nickName + "上线了");
                    pw.println(message);
                }
            }*/
            synchronized (allOutMap){
                // 遍历map
//                for(Map.Entry<String,PrintWriter> entry:allOutMap.entrySet()){
//                    entry.getValue().println(message);
//                }

                // 遍历map,只需要获取value即可
                for(PrintWriter pw:allOutMap.values()){
                    pw.println(message);
                }
            }

        }

        // 私聊方法
        private void sendMessageToSomeone(String message){
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

}
